﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using LibNoise;
using System.Drawing.Imaging;

namespace SampleImageGenerator.Rendering
{
	internal class HeightRenderer : IRenderer
	{
		public double AmbientLight { get; set; }
		public double DirectionalLight { get; set; }

		public HeightRenderer()
		{
			AmbientLight = 0.5;
			DirectionalLight = 0.5;
		}

		public System.Drawing.Image Render(IModule module, IPalette palette, System.Drawing.RectangleF sourceRegion, System.Drawing.Size size)
		{
			Int32 imageWidth = size.Width;
			Int32 imageHeight = size.Height;
			Bitmap image = new Bitmap(imageWidth, imageHeight, PixelFormat.Format32bppArgb);

			Double xScaleFactor = (Double)sourceRegion.Width / (Double)image.Width;
			Double yScaleFactor = (Double)sourceRegion.Height / (Double)image.Height;
			Double xOffset = sourceRegion.Left;
			Double yOffset = sourceRegion.Top;

			Double lx = 1;
			Double ly = -1;
			Double lz = -1;
			Double modLight = System.Math.Sqrt(lx * lx + ly * ly + lz * lz);
			Double lightX = lx / modLight;
			Double lightY = ly / modLight;
			Double lightZ = lz / modLight;

			Double zd = 0.0;
			Int32 imageWidthPlusOne = imageWidth + 1;
			Int32 imageHeightPlusOne = imageHeight + 1;
			Double[,] values = new Double[imageWidthPlusOne, imageHeightPlusOne];

			Double minValue = Double.MaxValue;
			Double maxValue = Double.MinValue;
			for (Int32 y = 0; y < imageHeightPlusOne; y++)
			{
				for (Int32 x = 0; x < imageWidthPlusOne; x++)
				{
					Double xd = xOffset + (x * xScaleFactor);
					Double yd = yOffset + (y * yScaleFactor);
					Double value = module.GetValue(xd, yd, zd);
					if (value > maxValue) { maxValue = value; }
					if (value < minValue) { minValue = value; }
					values[x, y] = value;
				}
			}

			palette.SetRange(minValue, maxValue);

			Rectangle imageRegion = new Rectangle(0, 0, imageWidth, imageHeight);
			BitmapData bitmapData = image.LockBits(imageRegion, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
			Int32 pixelSize = 4;

			unsafe
			{
				for (Int32 y = 0; y < imageHeight; y++)
				{
					Byte* row = (byte*)bitmapData.Scan0 + (y * bitmapData.Stride);

					for (Int32 x = 0; x < imageWidth; x++)
					{
						Double value = values[x, y];
						Color colour = palette.GetColour(value);

						Double xd = xOffset + (x * xScaleFactor);
						Double yd = yOffset + (y * yScaleFactor);

						// Calculate a rough normal by taking the cross
						// product of vectors in the X and Y directions.
						Double valueDx = values[x + 1, y] - value;
						Double valueDy = values[x, y + 1] - value;

						Double normalX = -yScaleFactor * valueDx;
						Double normalY = -xScaleFactor * valueDy;
						Double normalZ = xScaleFactor * yScaleFactor;

						Double modNormal = System.Math.Sqrt(normalX * normalX + normalY * normalY + normalZ * normalZ);

						Double unitNormalX = normalX / modNormal;
						Double unitNormalY = normalY / modNormal;
						Double unitNormalZ = normalZ / modNormal;

						Double directionalBrightness = DirectionalLight * ((lightX * unitNormalX) + (lightY * unitNormalY) + (lightZ * unitNormalZ));
						Double brightness = directionalBrightness + AmbientLight;

						if (brightness < AmbientLight)
						{
							brightness = AmbientLight;
						}

						if (brightness > 1)
						{
							brightness = 1;
						}

						Int32 red = (Int32)(colour.R * brightness);
						Int32 green = (Int32)(colour.G * brightness);
						Int32 blue = (Int32)(colour.B * brightness);
						Color dimmedColour = Color.FromArgb(colour.A, red, green, blue);

						row[x * pixelSize + 0] = dimmedColour.B;
						row[x * pixelSize + 1] = dimmedColour.G;
						row[x * pixelSize + 2] = dimmedColour.R;
						row[x * pixelSize + 3] = dimmedColour.A;
					}
				}

				image.UnlockBits(bitmapData);
			}

			return image; 
		}
	}
}
